﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

using Skyhook.Core;
using Skyhook.Core.Helpers;
using Skyhook.Core.Graphics;

namespace Skyhook.Landscape
{
    class LandscapeView
    {
        const string EffectName = "landscape";

        LandscapeModel Model = null;

        int WorldX = 0;
        int WorldY = 0;

        int ViewportWidth = 0;
        int ViewportHeight = 0;

        int TileX = 0;
        int TileY = 0;

        Rectangle CacheArea = new Rectangle();
        Vector2 ScreenPosition = new Vector2(0, 0);

        RenderTarget2D DrawCache = null;
        SpriteBatch SpriteBatch = null;

        bool NeedUpdateCache = true;

        Effect effect = null;
        IndexBuffer IndexBuffer = null;
        short[] IndexData = null;

        int TrianglesOnScreen = 0;

        public LandscapeView(LandscapeModel model)
        {
            Model = model;
        }

        public void Initialize()
        {
            ViewportWidth = GraphicsManager.ViewportWidth;
            ViewportHeight = GraphicsManager.ViewportHeight;

            DrawCache = GraphicsFactory.CreateRenderTarget2D(ViewportWidth, ViewportHeight, SurfaceFormat.Color, DepthFormat.None, 0, RenderTargetUsage.PreserveContents);
            SpriteBatch = GraphicsFactory.CreateSpriteBatch();

            CacheArea.X = 0;
            CacheArea.Y = 0;
            CacheArea.Width = ViewportWidth;
            CacheArea.Height = ViewportHeight;

            SetupEffect();
            SetupIndexes();
        }

        void SetupEffect()
        {
            effect = GraphicsFactory.CreateEffectFromFile(EffectName);

            effect.Parameters["ViewportWidth"].SetValue(ViewportWidth);
            effect.Parameters["ViewportHeight"].SetValue(ViewportHeight);
            effect.Parameters["LandTexture1"].SetValue(Model.LandTextures[0]);
            effect.Parameters["LandTexture2"].SetValue(Model.LandTextures[1]);
            effect.Parameters["LandTexture3"].SetValue(Model.LandTextures[2]);
            effect.Parameters["LandTexture4"].SetValue(Model.LandTextures[3]);
            effect.Parameters["LandTexture5"].SetValue(Model.LandTextures[4]);
            effect.Parameters["LandTexture6"].SetValue(Model.LandTextures[5]);
            effect.Parameters["MixTexture1"].SetValue(Model.MixTextures[0]);
            effect.Parameters["MixTexture2"].SetValue(Model.MixTextures[1]);
            effect.Parameters["ShiftX"].SetValue(0);
            effect.Parameters["ShiftY"].SetValue(0);
            effect.Parameters["Projection"].SetValue(IsoUtils.OrthohraphicProjectionMatrix);
            effect.Parameters["LandscapeToScreen"].SetValue(IsoUtils.LandscapeToScreenMatrix);

            effect.CurrentTechnique = effect.Techniques["Landscape"];
        }

        void SetupIndexes()
        {
            IndexBuffer = GraphicsFactory.CreateIndexBuffer(IndexElementSize.SixteenBits, 6 * (GraphicsManager.ViewportWidth / (int)IsoUtils.LandscapeTileWidth + 3) * (2 * GraphicsManager.ViewportHeight / (int)IsoUtils.LandscapeTileHeight + 1) * 2, BufferUsage.WriteOnly);
            IndexData = new short[6 * (GraphicsManager.ViewportWidth / (int)IsoUtils.LandscapeTileWidth + 3) * (2 * GraphicsManager.ViewportHeight / (int)IsoUtils.LandscapeTileHeight + 1) * 2];
            UpdateIndexBuffer();
        }

        void UpdateIndexBuffer()
        {
            int maxrownumber = ViewportHeight / (int)IsoUtils.LandscapeTileHeight + 2;
            int maxrowlength = ViewportWidth / (int)IsoUtils.LandscapeTileWidth + 3;

            int index = UpdateRows(TileX, TileY, maxrownumber, maxrowlength, 0);
            index = UpdateRows(TileX - 1, TileY, maxrownumber, maxrowlength, index);

            TrianglesOnScreen = index * 2;

            IndexBuffer.SetData<short>(IndexData);
        }

        int UpdateRows(int tilex, int tiley, int maxrownumber, int maxrowlength, int startindex)
        {
            int posx = tilex;
            int posy = tiley;

            int currentindex = startindex;
            int row = 0;
            int rowlength = 0;

            while (row < maxrownumber)
            {
                rowlength = 0;
                while (rowlength < maxrowlength)
                {
                    currentindex = SetIndexData(posx, posy, currentindex);

                    posx++;
                    posy--;

                    rowlength++;
                }

                row++;
                posx = tilex + row;
                posy = tiley + row;
            }

            return currentindex;
        }

        int SetIndexData(int posx, int posy, int currentindex)
        {
            if (posx >= 0 && posx < Model.MapWidth && posy >= 0 && posy < Model.MapHeight)
            {
                IndexData[currentindex * 6 + 0] = (short)(posy * (Model.MapWidth + 1) + posx);
                IndexData[currentindex * 6 + 1] = (short)(posy * (Model.MapWidth + 1) + 1 + posx);
                IndexData[currentindex * 6 + 2] = (short)((posy + 1) * (Model.MapWidth + 1) + 1 + posx);

                IndexData[currentindex * 6 + 3] = (short)((posy + 1) * (Model.MapWidth + 1) + 1 + posx);
                IndexData[currentindex * 6 + 4] = (short)((posy + 1) * (Model.MapWidth + 1) + posx);
                IndexData[currentindex * 6 + 5] = (short)(posy * (Model.MapWidth + 1) + posx);

                currentindex++;
            }

            return currentindex;
        }

        public void Draw(GameTime time)
        {
            UpdateCache();
            DrawCachedTexture();            
        }

        void UpdateCache()
        {
            if (NeedUpdateCache)
            {
                GraphicsManager.SetRenderTarget(DrawCache);
                GraphicsManager.ClearScreen(Color.Transparent);

                UpdateIndexBuffer();
                DrawModel();

                GraphicsManager.SetRenderTarget(null);

                NeedUpdateCache = false;
            }
        }

        void DrawModel()
        {
            if (TrianglesOnScreen > 0)
            {
                foreach (EffectPass pass in effect.CurrentTechnique.Passes)
                {
                    pass.Apply();
                    GraphicsManager.SetVertexBuffer(Model.VertexBuffer);
                    GraphicsManager.SetIndexBuffer(IndexBuffer);
                    GraphicsManager.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, TrianglesOnScreen * 3, 0, TrianglesOnScreen);
                }
            }
        }

        void DrawCachedTexture()
        {
            SpriteBatch.Begin();
            SpriteBatch.Draw(DrawCache, ScreenPosition, CacheArea, Color.White);
            SpriteBatch.End();
        }

        public void Scroll(int dx, int dy)
        {
            UpdateCoordinates(WorldX + dx, WorldY + dy);
        }

        void UpdateCoordinates(int worldx, int worldy)
        {
            if (WorldX != worldx || WorldY != worldy)
            {
                WorldX = worldx;
                WorldY = worldy;
                
                Vector2 tiles = IsoUtils.ConvertScreenToLandscapeTiles(worldx, worldy);

                TileX = (int)tiles.X;
                TileY = (int)tiles.Y;

                effect.Parameters["ShiftX"].SetValue(-WorldX);
                effect.Parameters["ShiftY"].SetValue(-WorldY);

                NeedUpdateCache = true;
            }
        }
    }
}
